package org.webscale.util.sdb;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.webscale.ApplicationException;
import org.webscale.util.AWSCredential;
import org.webscale.util.StringUtil;

import com.xerox.amazonws.sdb.Domain;
import com.xerox.amazonws.sdb.Item;
import com.xerox.amazonws.sdb.ItemAttribute;
import com.xerox.amazonws.sdb.QueryResult;
import com.xerox.amazonws.sdb.QueryWithAttributesResult;
import com.xerox.amazonws.sdb.SDBException;
import com.xerox.amazonws.sdb.SimpleDB;

public class SDBMapper<T> {
	private static final Logger log = Logger.getLogger(SDBMapper.class);
	MappingStrategy strategy;
	//SDBPipe pipe;
	AWSCredential credentials;
	SimpleDB sdb = null;
	boolean createDomainIfNotExists = true;
	SDBKeyGen keyGen;
	Class persistedClazz;
	
	public interface ItemResultCallback<T>{
		public void onNewRow(T obj);
	}
	
	public SDBMapper() {
		super();
	}
	
	public void iterateQuery(String query , ItemResultCallback<T> callback){
		String dname = strategy.getDomainName(persistedClazz);
		Domain domain  = loadDomain(dname);
		log.debug("Max connections " + domain.getMaxConnections());
		log.debug("Max threads " + domain.getMaxThreads());
		ArrayList<T> results = new ArrayList<T>();
		
		
		try {
			QueryResult result = domain.listItems(query);
			String token = result.getNextToken();
			List<Item> items = result.getItemList();
			for(Item item : items){
				int size = item.getAttributes().size();
				if(size > 0){
					Object obj = strategy.getPersistedClazzOnDomainName(dname).newInstance();
					strategy.mapItemToObject(item , obj);
					//results.add((T)obj);
					callback.onNewRow((T)obj);
				}
			}
			domain.listItems(query, token);
			
			
		} catch (SDBException e) {
			log.error("Error Quering object", e);
			throw new ApplicationException(e);
		} catch (InstantiationException e) {
			log.error("Error Quering object", e);
			throw new ApplicationException(e);
		} catch (IllegalAccessException e) {
			log.error("Error Quering object", e);
			throw new ApplicationException(e);
		}
	}
	/*
	public List<T> queryForObjects(String query , String nextToken){
		String dname = strategy.getDomainName(persistedClazz);
		Domain domain  = loadDomain(dname);
		log.info("Max connections " + domain.getMaxConnections());
		log.info("Max threads " + domain.getMaxThreads());
		ArrayList<T> results = new ArrayList<T>();
		try {
			QueryResult result = domain.listItems(query);
			String token = result.getNextToken();
			
			List<Item> items = result.getItemList();
			for(Item item : items){
				int size = item.getAttributes().size();
				if(size > 0){
					Object obj = strategy.getPersistedClazzOnDomainName(dname).newInstance();
					strategy.mapItemToObject(item , obj);
					results.add((T)obj);
				}
			
			}
		} catch (SDBException e) {
			log.error("Error Quering object", e);
			throw new ApplicationException(e);
		} catch (InstantiationException e) {
			log.error("Error Quering object", e);
			throw new ApplicationException(e);
		} catch (IllegalAccessException e) {
			log.error("Error Quering object", e);
			throw new ApplicationException(e);
		}
		
		return results;
	}
	
	*/
		
	/**
	 * USe this method when you have a limited result set .i.e about 100 elemements.
	 * 
	 * For more please use the call callback method above
	 * @param query
	 * @return
	 */
	public List<T> queryForObjects(String query){
		String dname = strategy.getDomainName(persistedClazz);
		Domain domain  = loadDomain(dname);
		ArrayList<T> results = new ArrayList<T>();
		try {
			QueryResult result = domain.listItems(query);
			String token = result.getNextToken();
			List<Item> items = result.getItemList();
			for(Item item : items){
				int size = item.getAttributes().size();
				if(size > 0){
					Object obj = strategy.getPersistedClazzOnDomainName(dname).newInstance();
					strategy.mapItemToObject(item , obj);
					results.add((T)obj);
				}
			}
		} catch (SDBException e) {
			log.error("Error Quering object", e);
			throw new ApplicationException(e);
		} catch (InstantiationException e) {
			log.error("Error Quering object", e);
			throw new ApplicationException(e);
		} catch (IllegalAccessException e) {
			log.error("Error Quering object", e);
			throw new ApplicationException(e);
		}
		
		return results;
	}
	
	public List<T> queryForObjectsWithSQL(String query){
		String dname = strategy.getDomainName(persistedClazz);
		Domain domain  = loadDomain(dname);
		ArrayList<T> results = new ArrayList<T>();
	    String nextToken = null;
		 
       
                 QueryWithAttributesResult qwar =null;
                 do {
                	
					try {
						qwar = domain.selectItems(query, nextToken);
					} catch (SDBException e) {
						log.error("Error Quering object", e);
						throw new ApplicationException(e);
					}
					Map<String, List<ItemAttribute>> items = qwar.getItems();
					for (String id : items.keySet()) {
        					Object obj =null;
							try {
								obj = strategy.getPersistedClazzOnDomainName(dname).newInstance();
							} catch (InstantiationException e) {
								log.error("Error Quering object", e);
								throw new ApplicationException(e);
							} catch (IllegalAccessException e) {
								log.error("Error Quering object", e);
								throw new ApplicationException(e);
							}
        					List<ItemAttribute> iaList = items.get(id);
							strategy.mapItemAttrsToOject(id, iaList, obj);
							results.add((T)obj);
						}
			         nextToken = qwar.getNextToken();
                	 
			         System.out.println("Box Usage :"+qwar.getBoxUsage());
                 	} while (nextToken != null && !nextToken.trim().equals(""));
         
         return results;
	}
	
	public void delete(String id){
		String dname = strategy.getDomainName(persistedClazz);
		Domain domain  = loadDomain(dname);
		try {
			domain.deleteItem(id);
		} catch (SDBException e) {
			log.error("Error deletting object", e);
			throw new ApplicationException(e);
		}
	}
	/**
	 * Returns the ID Associated to the object
	 * @param obj
	 * @return
	 */
	public String save(T obj) {
		String dname = strategy.getDomainName(persistedClazz);
		List<ItemAttribute> ias = strategy.getItemAttributes(obj);
		
		String id = null;
		// look for id
		for(ItemAttribute ia : ias){
			String nm = ia.getName().trim();
			if(nm.equalsIgnoreCase("id")){
				String v = ia.getValue();
				if(StringUtil.isValid(v) && !v.trim().equals("0")){
					id = ia.getValue();
				}
				log.info("Found ID in obj " + id);
			}
		}
		if(id == null){
			id = keyGen.getNextKeyValue() + "";
			// Case when ID is generated by the mapper
			
			log.debug("ID Generated " + id);
		}
		
		Domain domain  = loadDomain(dname);
		try {
			Item item = domain.getItem(id);
			item.putAttributes(ias);
		} catch (SDBException e) {
			log.error("Error while saving object", e);
			throw new ApplicationException(e);
		}
		return id;
	}

	public T findById(String id ) {
		String domainName = strategy.getDomainName(persistedClazz);
		Domain domain  = loadDomain(domainName , true);
		if(domain == null){
			log.error("Domain " + domainName + " does not exists");
			throw new ApplicationException("Domain " + domainName + " does not exists");
		}
		try {
			Item item = domain.getItem(id);
			int size = item.getAttributes().size();
			if(size == 0)
				return null;
			else{
				Object obj = strategy.getPersistedClazzOnDomainName(domainName).newInstance();
				strategy.mapItemToObject(item , obj);
				return (T)obj;
			}
		} catch (SDBException e) {
			log.error("Error Creating an item " ,e );
			throw new ApplicationException(e);
		} catch (InstantiationException e) {
			log.error("Error  " ,e );
			throw new ApplicationException(e);
		} catch (IllegalAccessException e) {
			log.error("Error  " ,e );
			throw new ApplicationException(e);
		}
		
	}
	

	public MappingStrategy getStrategy() {
		return strategy;
	}

	public void setStrategy(MappingStrategy strategy) {
		this.strategy = strategy;
	}
	
	private Domain loadDomain(String name , boolean create){
		Domain d = DomainObjectsCache.instance().get(name);
		log.info("Domain in Map = " + d);
		if(create){
			return loadDomain(name);
		}else
			return null;
	}
	
	private Domain loadDomain(String name){
		Domain d = DomainObjectsCache.instance().get(name);
		log.info("Domain in Map = " + d);
		if(d == null){
			SimpleDB db =getSDB();
			try {
				//d = db.getDomain(name);
				 d= db.createDomain(name);
				 DomainObjectsCache.instance().put(name, d);
				log.info("after createDomain = " + d.getUrl() + " name=" + 	d.getName());
			} catch (SDBException e) {
				log.warn("Error while creating Domain - " + name, e);
				throw new ApplicationException(e);
			}
			
		}
		return d;
	}
	
	private SimpleDB getSDB(){
		if(sdb == null)
			sdb = new SimpleDB(credentials.getAwsKey(), credentials.getAwsSecretKey());
		return sdb;
	}

	public SDBKeyGen getKeyGen() {
		return keyGen;
	}

	public void setKeyGen(SDBKeyGen keyGen) {
		this.keyGen = keyGen;
	}

	

	public boolean isCreateDomainIfNotExists() {
		return createDomainIfNotExists;
	}

	public void setCreateDomainIfNotExists(boolean createDomainIfNotExists) {
		this.createDomainIfNotExists = createDomainIfNotExists;
	}

	public Class getPersistedClazz() {
		return persistedClazz;
	}

	public void setPersistedClazz(Class persistedClazz) {
		this.persistedClazz = persistedClazz;
	}

	public AWSCredential getCredentials() {
		return credentials;
	}

	public void setCredentials(AWSCredential credentials) {
		this.credentials = credentials;
	}
	 StringBuilder ret = new StringBuilder();
	 private  String filter(String val) {
         if (val.length() == 0) return val;      // fast exit
         char [] chars = new char[val.length()];
         val.getChars(0, val.length(), chars, 0);
         for (int i=0; i<chars.length; i++) {
                 if (!(chars[i]>0 && chars[i]<128)) {
                         ret.append("\\u");
                         ret.append(Integer.toHexString(chars[i]));
                 }
                 else {
                         ret.append(chars[i]);
                 }
         }
         return ret.toString();
 }
	
}
